package LeetDev.a3d;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.view.MotionEvent;

/**
 * The Engine is a class that might be missnamed, but even though, it is still the main engine of the framework.
 * It should be constructed once, and then used as a singleton.
 * It will handle everything about EGL and OpenGL ES.
 * It will handle all events and event handlers.
 * @author Micael Hildenborg
 *
 */
public class Engine extends GLSurfaceView implements GLSurfaceView.Renderer {
	private long m_deltaTime,m_time=0;
	private int m_width,m_height;
	private float m_aspect;
	private Node[] m_world;
	private EventHandler m_handler;
	private boolean m_handlerInited=false;
	private static Engine s_engine;
	private Event m_eventQueue[]=new Event[32];
	private int m_eventNum=0;
	long m_deltas[] =new long[5];
	int m_deltapos=0;
	int m_maxLights=8;
	
	/**
	 * Class constructor.
	 * Should only be constructed once in a programs lifetime, as other parts of the framework considers the Engine to be a singleton.
	 * @param context the context the Engine will work with. This context will most likely be from the Activity the application is built around.
	 */
	public Engine(Context context)
	{
		super(context);
		m_eventNum=0;
		m_deltas[0]=m_deltas[1]=m_deltas[2]=m_deltas[3]=m_deltas[4]=0;
		s_engine=this;
		Mesh.initTransparencyList();
//		setDebugFlags(DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS);
//		setEGLConfigChooser(8,8,8,0,16,0);  
		setRenderer(this);
		setRenderMode(RENDERMODE_CONTINUOUSLY);
	}
	
	/**
	 * Empty method, might be used in the future. 
	 * @see android.opengl.GLSurfaceView.Renderer#onSurfaceCreated(javax.microedition.khronos.opengles.GL10, javax.microedition.khronos.egl.EGLConfig)
	 */
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // Do nothing special.
    }
    
    /**
     * Surface and OpenGL initialization. 
     * @see android.opengl.GLSurfaceView.Renderer#onSurfaceChanged(javax.microedition.khronos.opengles.GL10, int, int)
     */
    public void onSurfaceChanged(GL10 gl, int w, int h) {
        gl.glViewport(0, 0, w, h);
        m_width=w;
        m_height=h;
		float amb[]={1,1,1,1};	// max ambient as it is set in material later.
		gl.glLightModelfv(GL10.GL_LIGHT_MODEL_AMBIENT,amb,0);
		gl.glEnable(GL10.GL_DEPTH_TEST);
		gl.glDepthFunc(GL10.GL_LESS);
		gl.glEnable(GL10.GL_CULL_FACE);
		gl.glCullFace(GL10.GL_BACK);
		gl.glEnable(GL10.GL_NORMALIZE);
		gl.glEnable(GL10.GL_LIGHTING);
		gl.glLightModelf(GL10.GL_LIGHT_MODEL_TWO_SIDE,1.0f);
		int max[]=new int[1];
		gl.glGetIntegerv(GL10.GL_MAX_LIGHTS,max,0);
		m_maxLights=max[0];
		gl.glMatrixMode(GL10.GL_PROJECTION);
		gl.glLoadIdentity();
		gl.glOrthof(-1,1,1,-1,-1,1);	// default camera mode until other set by user.
		gl.glViewport(0,0,m_width,m_height);
		gl.glMatrixMode(GL10.GL_TEXTURE);
		float texturematrix[]={1,0,0,0, 0,-1,0,0, 0,0,1,0, 0,1,0,1};
		gl.glLoadMatrixf(texturematrix,0);
		m_aspect=(float)m_width/(float)m_height;
		if(!m_handlerInited)
		{
			m_handler.init(gl);
			m_handlerInited=true;
		}
    }

    /**
     * The per frame handling.
     * This method will handle timing, and updating of the screen as well as making sure that the queued events gets sent to the EventHandler.
     * @see android.opengl.GLSurfaceView.Renderer#onDrawFrame(javax.microedition.khronos.opengles.GL10)
	 * @see EventHandler
     */
    public void onDrawFrame(GL10 gl) {
    	if(m_time==0) {m_time=System.currentTimeMillis();}
		long delta=System.currentTimeMillis()-m_time;
		m_time+=delta;
		m_deltas[m_deltapos]=delta;
		m_deltapos=(m_deltapos+1)%5;
		delta=0;
		for(int i=5;--i>=0;)
		{
			delta+=m_deltas[i];
		}
		m_deltaTime=delta/5;
		
    	Event eventQueue[];
		synchronized(this)
		{
	    	eventQueue=new Event[m_eventNum];
	    	System.arraycopy(m_eventQueue, 0, eventQueue, 0, m_eventNum);
	    	for(int i=m_eventNum;--i>=0;) {m_eventQueue[i]=null;}
	    	m_eventNum=0;
		}
    	for(int i=0;i<eventQueue.length;++i)
    	{
    		// We might have several different eventhandlers in the program. And we might want to change eventhandler from the current eventhandler...
			EventHandler tmphandler=m_handler;
			tmphandler.handleEvent(eventQueue[i]);
			if(!tmphandler.equals(m_handler))
			{
				tmphandler.destruct(gl);
				if(!m_handlerInited)
				{
					m_handler.init(gl);
					m_handlerInited=true;
				}
			}
    	}
    	// Call frame method in the eventhandler, so we can update continuously.
    	double timeSeconds,deltaSeconds;
    	timeSeconds=(double)m_time/1000;
    	deltaSeconds=(double)m_deltaTime/1000;
		m_handler.frameEvent(gl,timeSeconds,deltaSeconds);
		
		if(null!=m_world)
		{
			try
			{
				animate(gl,timeSeconds,deltaSeconds);
				draw(gl);
			}
			catch(Exception e)
			{
				// What should we do with painting exceptions?
				System.out.println(e.toString());
				System.out.println(e.getMessage());
				System.out.println("Paint error!");
			}
		}
    }

    /**
     * Not finished. Specification not defined yet.
     */
    public boolean onTouchEvent(final MotionEvent event)
    {
//    	pushEvent(new Event(Event.MOTION,event.getX(),event.getY()));
        return true;
    }
    
    /**
     * @return the current aspect ratio of the screen calculated from width/height.
     */
	public float getAspect()
	{
		return (m_aspect);
	}
	/**
	 * 
	 * @return the max number of lights the system can handle.
	 */
	public int GetMaxLights()
	{
		return(m_maxLights);
	}
	
	/**
	 * As the Engine class is supposed to be used as a singleton, this method is used to get the instance of the class.
	 * @return instance of the Engine class.
	 */
	public static Engine instance()
	{
		return (s_engine);
	}

	/**
	 * The drawing and animation of the scene needs a world. This method will give the Engine a world to work with. The first position in the world array must be a Camera.
	 * @param world array of Node inherited objects such as Light, Camera and GeomObject.
	 * @see Node
	 * @see Camera
	 * @see Light
	 * @see GeomNode
	 */
	public void setWorld(Node[] world)
	{
		m_world=world;
	}
	/**
	 * Set the current EventHandler. The EventHandler can in many cases be thought of as the current working application. It is where all loading and unloading of resources should be done as well as updating them.
	 * @param handler inherited EventHandler.
	 * @see EventHandler
	 */
	public void setEventHandler(EventHandler handler)
	{
		m_handler=handler;
		m_handlerInited=false;
	}

	private void animate(GL10 gl, double time, double deltaTime) throws Exception
	{
		if(m_world!=null && m_world.length!=0)
		{
			if(!(m_world[0] instanceof Camera))
			{
				throw new Exception("The first Node in a world must be a Camera!");
			}
			for(int i=0;i<m_world.length;++i)
			{
				m_world[i].animate(gl, time, deltaTime);
			}
		}
	}
	
	private void draw(GL10 gl)
	{
		Mesh.clearTransparencyList();
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
		
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);
		for(int i=0;i<m_world.length;++i)
		{
			if(m_world[i] instanceof Camera && i!=0)
			{
				Mesh.drawTransparencyList(gl);
				gl.glClear(GL10.GL_DEPTH_BUFFER_BIT);
			}
			m_world[i].draw(gl);
		}
		Mesh.drawTransparencyList(gl);
	}

}
